package com.lanyou.cook.upload.service.socket.upload;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.log4j.Logger;

import com.lanyou.cook.upload.commons.Constants;
import com.lanyou.cook.upload.util.ExceptionToStrFactory;
import com.lanyou.cook.upload.util.PropFactory;

public class HandlerThread implements Runnable {
	private static Logger logger = Logger.getLogger(HandlerThread.class); 
	private Socket socket;
	private String filePath;

	/**
	 * @param client 根据Socket实例创建线程，并启动；默认上传路径是根目录
	 */
	public HandlerThread(Socket client) {
		socket = client;
		this.filePath = "/";
		new Thread(this).start();
	}

	/**
	 * @param client Socket实例
	 * @param filePath 上传文件的路径
	 * 根据Socket实例、上传文件路径创建线程，并启动
	 */
	public HandlerThread(Socket client, String filePath) {
		this.filePath = filePath;
		socket = client;
		new Thread(this).start();
	}

	@Override
	public void run() {
		//定义从客户端读数据的输入流
		InputStream is = null;
		//定义给客户端发生消息的写出流
		OutputStream out = null;
		//定义写入文件的写出流
		FileOutputStream fos = null;
		try {
			//获得socket客户端信息的输入流
			is = socket.getInputStream();
			//获得socket客户端写信息的输出流
			out = socket.getOutputStream();
			while (true) {
				//创建存放读socket客户端传入的字节数组，其大小规定一次最多读入量
				byte[] b = new byte[Integer
						.parseInt(PropFactory
								.getPropByKey(Constants.SOCKET_INPUTSTREAM_SIZE_FOR_FILENAME))];
				// 得到文件名：MD5：文件长度
				int a = is.read(b);
				//将第一次得到的信息按字符串数组保存
				String[] hearStrs = new String(b, 0, a).split(":");
				//判断操作系统
				String os = System.getProperty("os.name"); 

				//获取文件名
				String filename;
				if(os.toLowerCase().startsWith("win")){
					filename = new String(hearStrs[0].getBytes("gbk"),"utf-8");
				}else{
					filename = hearStrs[0];
				}
				//获取MD5值
				String md5str = hearStrs[1];
				//获取文件长度
				String filelength = hearStrs[2];
				logger.info("[HandlerThread.run]:接受到的文件名为：" + filename+"--md5:"+md5str+"--filelength:"+filelength);
				out.write("fileOk".getBytes());
				//创建上传文件的写出流
				fos = new FileOutputStream(filePath + filename);
				//创建存放读socket客户端传入的字节数组，其大小规定一次最多读入量
				byte[] b2 = new byte[Integer
										.parseInt(PropFactory
												.getPropByKey(Constants.SOCKET_INPUTSTREAM_SIZE_FOR_FILE))];
				int length = 0;
				int fileLenth = 0;
				while ((length = is.read(b2)) != -1) {
					fileLenth += length;
					// 把socket输入流写到文件输出流中去
					fos.write(b2, 0, length);
					//如果写入文件的长度等于客户端文件的长度，说明文件传输完成，可以进行MD5校验
					if (fileLenth == Integer.parseInt(filelength)) {
						if (fileCheck(filePath + filename, md5str)) {
							logger.info("[HandlerThread.run]:检验成功");
							out.write("ok".getBytes());
						} else {
							logger.info("[HandlerThread.run]:检验失败");
							out.write("fail".getBytes());
						}
					}
				}
				break;
			}

		} catch (IOException e) {
			logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
		} finally {
			//关闭流及socket
			try {
				if (fos != null) {
					fos.close();
				}
				is.close();
				out.close();
				socket.close();
			} catch (IOException e) {
				logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
			}
		}

	}

	/**
	 * @param md5bytes 用于生成md5值的字节数组
	 * @return 根据字节数组生成的MD5值
	 */
	private String md5ToString(byte[] md5bytes) {
		StringBuilder ret = new StringBuilder(md5bytes.length << 1);
		for (int i = 0; i < md5bytes.length; i++) {
			ret.append(Character.forDigit((md5bytes[i] >> 4) & 0xf, 16));
			ret.append(Character.forDigit(md5bytes[i] & 0xf, 16));
		}
		return ret.toString();
	}

	/**
	 * @param filePath 校验文件的路径
	 * @param md5Strc 客户端的md5值
	 * @return 所要校验文件的MD5值与客户端文件的MD5值的对比结果
	 */
	private boolean fileCheck(String filePath, String md5Strc) {
		MessageDigest md;
		InputStream in = null;
		try {
			//创建生成MD5的实例
			md = MessageDigest.getInstance("MD5");
			//创建读文件的File实例
			File file = new File(filePath);
			//创建读文件的InputStream实例
			in = new FileInputStream(file);
			//按文件的长度创建存放文件数据的字节数组
			byte[] b3 = new byte[(int) file.length()];
			//将文件读入字节数组
			in.read(b3);
			//生成MD5值
			String md5Str = md5ToString(md.digest(b3));
			logger.info("[HandlerThread.run]:"+file.getPath()+"--文件长度："+file.length()+"--MD5:"+md5Str);
			//返回对比结果
			return md5Str.equals(md5Strc);
		} catch (NoSuchAlgorithmException e) {
			logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
		} catch (FileNotFoundException e) {
			logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
		} catch (IOException e) {
			logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
		} finally {
			//关闭流
			try {
				in.close();
			} catch (IOException e) {
				logger.error("[HandlerThread.run]:"+ExceptionToStrFactory.getErrorInfoFromException(e));
			}
		}
		return true;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

}
